/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package httpserver

import (
	"crypto/tls"
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"net/http"
	"os"
	"reflect"
	"testing"
	"time"

	"github.com/agiledragon/gomonkey/v2"
	"github.com/smartystreets/goconvey/convey"
	"github.com/stretchr/testify/assert"
	"github.com/valyala/fasthttp"
	"yuanrong.org/kernel/runtime/libruntime/api"

	"yuanrong.org/kernel/pkg/common/faas_common/etcd3"
	"yuanrong.org/kernel/pkg/common/faas_common/localauth"
	commtls "yuanrong.org/kernel/pkg/common/faas_common/tls"
	commonTypes "yuanrong.org/kernel/pkg/common/faas_common/types"
	"yuanrong.org/kernel/pkg/functionscaler"
	"yuanrong.org/kernel/pkg/functionscaler/config"
	"yuanrong.org/kernel/pkg/functionscaler/registry"
	"yuanrong.org/kernel/pkg/functionscaler/types"
)

type mockListener struct{}

func (m *mockListener) Accept() (net.Conn, error) {
	return nil, fmt.Errorf("failed to accept")
}

func (m *mockListener) Close() error {
	return nil
}
func (m *mockListener) Addr() net.Addr {
	return nil
}

func TestStartHTTPServer(t *testing.T) {
	rawConfig := config.GlobalConfig
	defer func() {
		config.GlobalConfig = rawConfig
	}()
	convey.Convey("TestStartHTTPServer", t, func() {
		os.Setenv("POD_IP", "127.0.0.1")
		config.GlobalConfig = types.Configuration{
			HTTPSConfig: &commtls.InternalHTTPSConfig{
				HTTPSEnable: false},
			ModuleConfig: &types.ModuleConfig{ServicePort: "8889"},
		}
		errChan := make(chan error, 1)
		_, err := StartHTTPServer(errChan)
		convey.So(err, convey.ShouldBeNil)
		time.Sleep(1 * time.Second)
	})

	convey.Convey("TestStartHTTPSServer", t, func() {
		os.Setenv("POD_IP", "127.0.0.1")
		config.GlobalConfig = types.Configuration{
			HTTPSConfig: &commtls.InternalHTTPSConfig{
				HTTPSEnable: true},
			ModuleConfig: &types.ModuleConfig{ServicePort: "8899"},
		}
		defer gomonkey.ApplyFunc(commtls.InitTLSConfig, func(config commtls.InternalHTTPSConfig) error {
			return nil
		}).Reset()
		defer gomonkey.ApplyFunc(commtls.GetClientTLSConfig, func() *tls.Config {
			return &tls.Config{}
		}).Reset()
		errChan := make(chan error, 1)
		_, err := StartHTTPServer(errChan)
		convey.So(err, convey.ShouldBeNil)
		time.Sleep(1 * time.Second)
	})

}

func TestStartServer_InvalidPodIP(t *testing.T) {
	originalPodIP := os.Getenv("POD_IP")
	defer os.Setenv("POD_IP", originalPodIP)

	os.Setenv("POD_IP", "invalid_ip")

	httpServer := &fasthttp.Server{}

	patches := gomonkey.NewPatches()
	defer patches.Reset()

	err := startServer(httpServer)

	assert.NotNil(t, err)
	assert.Equal(t, "failed to get pod ip", err.Error())
}

func TestStartServer_FastHTTPListenAndServeTLS_Error(t *testing.T) {
	os.Setenv("POD_IP", "127.0.0.1")
	defer os.Unsetenv("POD_IP")

	httpServer := &fasthttp.Server{}
	config.GlobalConfig = types.Configuration{
		HTTPSConfig: &commtls.InternalHTTPSConfig{
			HTTPSEnable: true},
		ModuleConfig: &types.ModuleConfig{ServicePort: "8080"},
	}

	patches := gomonkey.NewPatches()
	defer patches.Reset()

	patches.ApplyFunc(net.Listen, func(network, address string) (net.Listener, error) {
		return nil, fmt.Errorf("err")
	})

	err := startServer(httpServer)

	assert.NotNil(t, err)
}

func TestStartServer_ListenAndServe_Error(t *testing.T) {
	os.Setenv("POD_IP", "127.0.0.1")
	defer os.Unsetenv("POD_IP")

	httpServer := &fasthttp.Server{}

	config.GlobalConfig = types.Configuration{
		HTTPSConfig: &commtls.InternalHTTPSConfig{
			HTTPSEnable: false},
		ModuleConfig: &types.ModuleConfig{ServicePort: "8080"},
	}

	mockError := errors.New("mocked ListenAndServe error")

	patches := gomonkey.NewPatches()
	defer patches.Reset()

	patches.ApplyMethod(reflect.TypeOf(httpServer), "ListenAndServe", func(_ *fasthttp.Server, addr string) error {
		return mockError
	})

	err := startServer(httpServer)

	assert.NotNil(t, err)
	assert.Equal(t, mockError, err)
}

func TestRout(t *testing.T) {
	rawConfig := config.GlobalConfig
	defer func() {
		config.GlobalConfig = rawConfig
	}()
	patches := []*gomonkey.Patches{
		gomonkey.ApplyFunc(etcd3.GetRouterEtcdClient, func() *etcd3.EtcdClient { return &etcd3.EtcdClient{} }),
		gomonkey.ApplyFunc(etcd3.GetMetaEtcdClient, func() *etcd3.EtcdClient { return &etcd3.EtcdClient{} }),
		gomonkey.ApplyFunc(etcd3.GetCAEMetaEtcdClient, func() *etcd3.EtcdClient { return &etcd3.EtcdClient{} }),
	}
	defer func() {
		for _, patch := range patches {
			time.Sleep(100 * time.Millisecond)
			patch.Reset()
		}
	}()
	convey.Convey("TestRout", t, func() {
		convey.Convey("auth failed", func() {
			config.GlobalConfig = types.Configuration{
				AuthenticationEnable: true,
			}
			defer gomonkey.ApplyFunc(localauth.AuthCheckLocally, func(ak string, sk string,
				requestSign string, timestamp string, duration int) error {
				return errors.New("auth failed")
			}).Reset()
			ctx := &fasthttp.RequestCtx{}
			route(ctx)
			convey.So(ctx.Response.StatusCode(), convey.ShouldEqual, http.StatusUnauthorized)
		})
		convey.Convey("path error", func() {
			config.GlobalConfig = types.Configuration{}
			ctx := &fasthttp.RequestCtx{}
			ctx.Request.URI().SetPath("/acquire")
			route(ctx)
			convey.So(ctx.Response.StatusCode(), convey.ShouldEqual, http.StatusInternalServerError)
		})
		convey.Convey("invoke unmarshal body error ", func() {
			config.GlobalConfig = types.Configuration{}
			ctx := &fasthttp.RequestCtx{}
			ctx.Request.URI().SetPath(invokePath)
			ctx.Request.SetBody([]byte("aaa"))
			route(ctx)
			convey.So(ctx.Response.StatusCode(), convey.ShouldEqual, http.StatusInternalServerError)
		})
		convey.Convey("invoke scheduler is nil ", func() {
			config.GlobalConfig = types.Configuration{}
			ctx := &fasthttp.RequestCtx{}
			ctx.Request.URI().SetPath(invokePath)
			args := []api.Arg{{Type: 1, Data: []byte("aaa")}}
			body, _ := json.Marshal(args)
			ctx.Request.SetBody(body)
			route(ctx)
			convey.So(ctx.Response.StatusCode(), convey.ShouldEqual, http.StatusInternalServerError)
		})
		convey.Convey("invoke ProcessInstanceRequestLibruntime success ", func() {
			defer gomonkey.ApplyMethod(reflect.TypeOf(&functionscaler.FaaSScheduler{}),
				"ProcessInstanceRequestLibruntime", func(_ *functionscaler.FaaSScheduler,
					args []api.Arg, traceID string) ([]byte, error) {
					return json.Marshal(&commonTypes.InstanceResponse{})
				}).Reset()
			defer gomonkey.ApplyFunc((*etcd3.EtcdWatcher).StartList, func(ew *etcd3.EtcdWatcher) {
				ew.ResultChan <- &etcd3.Event{
					Type:      etcd3.SYNCED,
					Key:       "",
					Value:     nil,
					PrevValue: nil,
					Rev:       0,
					ETCDType:  "",
				}
			}).Reset()
			defer gomonkey.ApplyFunc((*registry.FaasSchedulerRegistry).WaitForETCDList, func() {}).Reset()
			config.GlobalConfig = types.Configuration{}
			ctx := &fasthttp.RequestCtx{}
			ctx.Request.URI().SetPath(invokePath)
			args := []api.Arg{{Type: 1, Data: []byte("aaa")}}
			stopCh := make(chan struct{})
			registry.InitRegistry(stopCh)
			functionscaler.InitGlobalScheduler(stopCh)
			body, _ := json.Marshal(args)
			ctx.Request.SetBody(body)
			route(ctx)
			convey.So(ctx.Response.StatusCode(), convey.ShouldEqual, http.StatusOK)
		})
	})
}

func TestFastHTTPListenAndServeTLS(t *testing.T) {
	server := fasthttp.Server{
		TLSConfig: &tls.Config{},
	}
	convey.Convey("FastHTTPListenAndServeTLS failed", t, func() {
		patch := gomonkey.ApplyFunc(net.Listen, func(network, address string) (net.Listener, error) {
			return &mockListener{}, errors.New("listen fail")
		})
		defer patch.Reset()
		err := fastHTTPListenAndServeTLS("123", &server)
		convey.So(err.Error(), convey.ShouldEqual, "listen fail")
	})
	convey.Convey("FastHTTPListenAndServeTLS server is nil", t, func() {
		patch := gomonkey.ApplyFunc(net.Listen, func(network, address string) (net.Listener, error) {
			return &mockListener{}, nil
		})
		defer patch.Reset()
		err := fastHTTPListenAndServeTLS("123", nil)
		convey.So(err.Error(), convey.ShouldEqual, "server or tls config is nil")
	})
}
